package xfuzzy.alg_genetico.indeterminado.xfghlOld.model;

import java.util.Random;

//import sun.security.krb5.internal.crypto.crc32;
import xfuzzy.alg_genetico.indeterminado.xfghlOld.model.hierStruct.XfghlHierStruct;
import xfuzzy.alg_genetico.indeterminado.xfghlOld.model.spec.XfghlSpecification;
import xfuzzy.lang.Specification;
import xfuzzy.lang.XflException;

public class XfghlChromosome {

	/**
	 * Identificador del cromosoma
	 */
	private int id;

	/**
	 * El cromosoma se representa con un array de enteros
	 */
	private int[] chromosome;

	/**
	 * Estructura jerarquica que representa al cromosoma
	 */
	private XfghlHierStruct hierStruct = null;

	/**
	 * Objeto aleatorio
	 */
	private Random random;

	/**
	 * Numero de variables de la estructura La longitud del cromosoma es
	 * numVar-2
	 */
	private int numVar;

	/**
	 * Especificacion Xfuzzy que represenata al cromosoma
	 * 
	 */
	private Specification specification = null;

	/**
	 * Configuracion de la herramienta
	 */
	private XfghlConfig config = null;

	/**
	 * Variable que nos dira si un cromosoma ya ha sido entrenado
	 */
	private boolean learned = false;

	/**
	 * Error final
	 */
	private double finalError = -1;

	/**
	 * Crea un cromosoma
	 * 
	 * @param config
	 *            Configuracion del sistema
	 */
	public XfghlChromosome(XfghlConfig config) {
		this.numVar = config.getNumVarEnt();
		this.chromosome = new int[this.numVar - 2];
		this.random = XfghlSinglRandom.getRandom();
		this.config = config;

	}

	/**
	 * Crea un cromosoma
	 * 
	 * @param config
	 *            Configuracion del sistema
	 */
	public XfghlChromosome(XfghlConfig config, int[] chromosome, int id) {
		this.numVar = config.getNumVarEnt();
		this.chromosome = chromosome;
		this.random = XfghlSinglRandom.getRandom();
		this.config = config;
		this.id = id;

	}

	/**
	 * Constructor de copia, Devuelve un objeto copia
	 * 
	 * @param copy
	 */
	public XfghlChromosome(XfghlChromosome copy) {
		this.chromosome = copy.chromosome.clone();
		this.config = copy.config;
		this.finalError = copy.finalError;
		this.hierStruct = copy.hierStruct;
		this.learned = copy.learned;
		this.numVar = copy.numVar;
		this.random = copy.random;
		this.specification = copy.specification;
	}

	/**
	 * 
	 * @param numVar
	 *            numero de variables del sistema
	 * @param isRandom
	 *            Si se inicializa aleatoriamente o no
	 */
	public XfghlChromosome(XfghlConfig config, boolean isRandom) {
		this.numVar = config.getNumVarEnt();
		this.chromosome = new int[this.numVar - 2];
		this.random = XfghlSinglRandom.getRandom();
		this.config = config;

		if (isRandom) {
			initializeRandom();
		}
	}

	/**
	 * Getter and setter
	 * 
	 * @return
	 */
	public int[] getChromosome() {
		return chromosome;
	}

	public void setChromosome(int[] chromosome) {
		this.chromosome = chromosome;
		this.specification = null;
		this.hierStruct = null;
		this.learned = false;
		this.finalError = -1;
	}

	/**
	 * Generamos la estructura jerarquica una sola vez
	 * 
	 * @return
	 */
	public XfghlHierStruct getHierStruct() {

		if (this.hierStruct == null)
			generateHierStruct();
		return this.hierStruct;
	}

	public void setHierStruct(XfghlHierStruct hierStruct) {
		this.hierStruct = hierStruct;
	}

	/**
	 * Generamos la estructura jerarquica Xfghl
	 * 
	 */
	private void generateHierStruct() {
		// Crear estructura inicial
		this.hierStruct = new XfghlHierStruct();

		// Generamos la Estructura Jerarquica
		for (int pos = 0; pos < this.chromosome.length; pos++) {
			hierStruct.insert(this.chromosome[pos]);
		}
	}

	/**
	 * Cada valor del array tiene que estar entre 0 y su posicion + 3 Sistema
	 * -Variables = N - Numero de conexiones = 2*N -1 Para una pos del cromosoma.
	 * -Sistema con variables = (pos + 3) -Posibles conexiones = 2*(pos + 2) -1
	 * 
	 * @return true si el cromosoma es correcto false el cromosoma no es
	 *         correcto
	 */
	public boolean isCorrect() {
		// Variable de salida
		// Recorremos
		for (int pos = 0; pos < this.chromosome.length; pos++) {
			if (this.chromosome[pos] < 0 || this.chromosome[pos] >= numConexionesNVar(pos)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Calcula el numero de conexiones para una posicion del cromosoma
	 * 
	 * @param pos
	 *            indice del array
	 * @return numero de conexiones
	 */
	private int numConexionesNVar(int pos) {
		return (2 * (pos + 2));
	}

	/**
	 * Inicializa un cromosoma aleatoriamente
	 */
	private void initializeRandom() {
		for (int i = 0; i < this.chromosome.length; i++) {
			this.chromosome[i] = this.random.nextInt(numConexionesNVar(i) - 1);
		}
	}

	/**
	 * Sobrecarga de toString que pone los elementos del array en un string
	 */
	public String toString2() {
		StringBuffer sal = new StringBuffer();

		for (int i = 0; i < this.chromosome.length; i++)
			sal.append(chromosome[i]);
		return sal.toString();

	}

	/**
	 * M�todo que presenta el array en formato texto
	 */
	public String toString() {
		StringBuffer sal = new StringBuffer();

		sal.append(+this.id + " [");
		// sal.append("[");
		for (int i = 0; i < this.chromosome.length; i++) {
			sal.append(chromosome[i]);
			if (i < this.chromosome.length - 1)
				sal.append(", ");

		}
		sal.append("]");
		return sal.toString();

	}

	/**
	 * Generamos la especificaci�n s�lo una vez
	 * 
	 * @return
	 */
	public synchronized Specification getSpecification() {

		if (this.specification == null)
			generateSpecification();
		return specification;
	}

	public void setSpecification(Specification specification) {
		this.specification = specification;
	}

	/**
	 * Generamos la Specificacion XFuzzy
	 */
	private void generateSpecification() {

		if (isCorrect()) {
			// Generamos la especificaci�n xfuzzy
			getHierStruct();

			try {
				this.specification = XfghlSpecification.create(this.hierStruct,
						this.config);
			} catch (XflException e) {
				e.printStackTrace();
			}

		}

	}

	/**
	 * @return the learned
	 */
	public boolean isLearned() {
		return learned;
	}

	/**
	 * @param finalError
	 *            the finalError to set
	 */
	public void setFinalError(double finalError) {
		this.finalError = finalError;
		this.learned = true;
	}

	/**
	 * @return the finalError
	 */
	public double getFinalError() {
		return finalError;
	}

	/**
	 * Funci�n que muta un gen del cromosoma con la misma probabilidad para cada
	 * uno.
	 */
	public void mutate1() {
		String ant = this.toString();

		// Decimos que ya no est� entrenada
		this.learned = false;
		this.finalError = -1;
		this.specification = null;
		this.hierStruct = null;

		// Vemos indice del gen a mutar
		int gen = this.random.nextInt(this.chromosome.length);

		// Capturamos el valor
		int valorAnt = this.chromosome[gen];

		// Buscamos un nuevo valor para el gen
		boolean encontrado = false;
		int valorNew = -1;

		while (!encontrado) {
			valorNew = this.random.nextInt(numConexionesNVar(gen) - 1);
			if (valorAnt != valorNew)
				encontrado = true;
		}

		// Asignamos el nuevo valor
		this.chromosome[gen] = valorNew;

		System.out.println("Mutado: " + ant + " por " + toString());
	}

	/**
	 * Funci�n que muta un gen del cromosoma con m�s probabilidad para los
	 * elementos m�s a la derecha.
	 */
	public void mutate2() {
		String ant = this.toString();

		// Decimos que ya no esta entrenada
		this.learned = false;
		this.finalError = -1;
		this.specification = null;
		this.hierStruct = null;

		// Calculamos el valor maximo para hacer dar mayor peso a algunos genes
		int sumaInd = 0;

		for (int i = 0; i < this.chromosome.length; i++) {
			sumaInd += i;
		}

		// Valor aleatorio
		int prob = this.random.nextInt(sumaInd);

		boolean terminar = false;
		sumaInd = 0;
		int gen = -1;

		// Vemos el gen a mutar
		for (int i = 0; i < this.chromosome.length && !terminar; i++) {
			sumaInd += i;
			if (sumaInd <= prob) {
				terminar = true;
				gen = i;
			}
		}

		// Capturamos el valor
		int valorAnt = this.chromosome[gen];

		// Buscamos un nuevo valor para el gen
		boolean encontrado = false;
		int valorNew = -1;

		while (!encontrado) {
			valorNew = this.random.nextInt(numConexionesNVar(gen) - 1);
			if (valorAnt != valorNew)
				encontrado = true;
		}

		// Asignamos el nuevo valor
		this.chromosome[gen] = valorNew;

		System.out.println("Mutado: " + ant + " por " + toString());
	}

	/**
	 * Get learned
	 * 
	 * @param learned
	 */
	public void setLearned(boolean learned) {
		this.learned = learned;
	}

	/**
	 * Operador de cruce
	 * 
	 * @param c
	 *            otro cromosoma con el que se va a cruzar
	 * @return
	 */
	public XfghlChromosome[] cross(XfghlChromosome c) {

		// Elegimos el �ndice a dividir los cromosomas
		int indGen = this.random.nextInt(this.chromosome.length);

		int[] aC0 = new int[this.chromosome.length];
		int[] aC1 = new int[this.chromosome.length];

		// Recorremos el cromosoma completo
		for (int i = 0; i < this.chromosome.length; i++) {
			// Dejamos la primera mitad de los cromosomas iguales
			if (i <= indGen) {
				aC0[i] = this.chromosome[i];
				aC1[i] = c.getChromosome()[i];
			}
			// Cambiamos de sitio los genes
			else {
				aC0[i] = c.getChromosome()[i];
				aC1[i] = this.chromosome[i];
			}
		}

		// Cromosomas a devolver
		XfghlChromosome[] chrom = new XfghlChromosome[2];

		chrom[0] = new XfghlChromosome(this.config);
		chrom[0].setChromosome(aC0);
		chrom[1] = new XfghlChromosome(this.config);
		chrom[1].setChromosome(aC1);

		return chrom;
	}

	/**
	 * @param id
	 *            the id to set
	 */
	public void setId(int id) {
		this.id = id;
	}

	/**
	 * @return the id
	 */
	public int getId() {
		return id;
	}
}
